package ru.ifmo.mlarinsky.classificator.accuracy;

import java.util.*;
import java.util.Map.Entry;
import ru.ifmo.mlarinsky.classificator.io.IOUtil;
import ru.ifmo.mlarinsky.classificator.io.InstrumentLoader;
import ru.ifmo.mlarinsky.classificator.io.InstrumentLoader.Period;
import ru.ifmo.mlarinsky.classificator.math.MathUtil;
import ru.ifmo.mlarinsky.classificator.model.Instrument;
import ru.ifmo.mlarinsky.classificator.model.QuoteDate;
import ru.ifmo.mlarinsky.classificator.model.Vector;
import ru.ifmo.mlarinsky.classificator.providers.DeterminationVectorProvider;

public class ClassificationEfficiencyCalculator {

    private static final Map<String, String> DJ_CLASSIFICATION = new HashMap<String, String>();
    private static final int MAX_POINTS = 1;
    private static final Map<String, String[]> STANDARD = new HashMap<String, String[]>();
    private static final int SIGNIFICANCE_THRESHOLD = 50;

    static {
        DJ_CLASSIFICATION.put("DJUSBM", "Business Materials");
        DJ_CLASSIFICATION.put("DJUSNC", "Consumer Goods");
        DJ_CLASSIFICATION.put("DJUSCY", "Consumer Services");
        DJ_CLASSIFICATION.put("DJUSFN", "Financials");
        DJ_CLASSIFICATION.put("DJUSHC", "Health Care");
        DJ_CLASSIFICATION.put("DJUSIN", "Industrial");
        DJ_CLASSIFICATION.put("DJUSEN", "Oil & Gas");
        DJ_CLASSIFICATION.put("DJUSTC", "Technology");
        DJ_CLASSIFICATION.put("DJUSTL", "Telecommunications");
        DJ_CLASSIFICATION.put("DJUSUT", "Utilities");

        STANDARD.put("A", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("AA", new String[]{"XLB", "DJUSBM"});
        STANDARD.put("AAPL", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("AMZN", new String[]{"DJUSCY"});
        STANDARD.put("AXP", new String[]{"XLF", "DJUSFN"});
        STANDARD.put("BA", new String[]{"DJUSIN"});
        STANDARD.put("C", new String[]{"XLF", "DJUSFN"});
//        STANDARD.put("CAT", new String[]{});
        STANDARD.put("DD", new String[]{"XLB", "DJUSBM"});
        STANDARD.put("DIS", new String[]{"DJUSCY"});
//        STANDARD.put("EK", new String[]{});
        STANDARD.put("GE", new String[]{"XLF", "DJUSFN"});
        STANDARD.put("GM", new String[]{"XLY", "DJUSCY"});
        STANDARD.put("GOOG", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("HD", new String[]{"DJUSCY"});
        STANDARD.put("HON", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("HPQ", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("IBM", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("INTC", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("IP", new String[]{"XLB", "DJUSBM"});
        STANDARD.put("JNJ", new String[]{"IYH", "DJUSHC"});
        STANDARD.put("JPM", new String[]{"XLF", "DJUSFN"});
        STANDARD.put("KO", new String[]{"XLP", "DJUSNC"});
        STANDARD.put("MCD", new String[]{"DJUSCY"});
//        STANDARD.put("MMM", new String[]{});
//        STANDARD.put("MO", new String[]{});
        STANDARD.put("MRK", new String[]{"IYH", "DJUSHC"});
        STANDARD.put("MSFT", new String[]{"XLK", "DJUSTC"});
        STANDARD.put("PG", new String[]{"XLP", "DJUSNC"});
        STANDARD.put("T", new String[]{"DJUSCY"});
        STANDARD.put("TXN", new String[]{"XLK", "DJUSTC"});
//        STANDARD.put("UTX", new String[]{});
        STANDARD.put("WMT", new String[]{"DJUSCY"});
        STANDARD.put("XOM", new String[]{"XLE", "DJUSEN"});
        STANDARD.put("YHOO", new String[]{"XLK", "DJUSCY"});
    }

    private ClassificationEfficiencyCalculator() {
    }

    public static String getSector(Vector vector) {
        List<Entry<String, Double>> sortedIndexes = vector.getSortedCoordinates();
        Iterator<Entry<String, Double>> iterator = sortedIndexes.iterator();

        Entry<String, Double> nextEntry = iterator.next();
        String sectorIndex = nextEntry.getKey();

        System.out.println("");
        while (iterator.hasNext()) {
            System.out.print(nextEntry.getKey() + " - " + nextEntry.getValue() + "\n");
            nextEntry = iterator.next();
        }

        return DJ_CLASSIFICATION.get(sectorIndex);
    }

    private static void printClassification(DeterminationVectorProvider provider) {

        List<Vector> vectors = provider.getVectors();

        for (Vector vector : vectors) {
            System.out.print(vector.getLabel() + " -> " + getSector(vector) + "\n");
            System.out.println("Significant = " + checkSignificance(vector));
            System.out.println("Points = " + getPoints(vector));
            System.out.println("Standard = " + Arrays.toString(STANDARD.get(vector.getLabel())));
        }
    }

    private static int getPoints(Vector vector) {
//        if (!checkSignificance(vector)) {
//            return 0;
//        }

        String instrumentSymbol = vector.getLabel();
        String[] standard = STANDARD.get(instrumentSymbol);
        Collection<String> standardIndexes = Arrays.asList(standard == null ? new String[] {} : standard);
        List<Entry<String, Double>> sortedIndexes = vector.getSortedCoordinates();

        int points = MAX_POINTS;
        for (Entry<String, Double> entry : sortedIndexes) {
            if (points == 0 || standardIndexes.contains(entry.getKey()))
                return points;

            points--;
        }

        return 0;
    }

    public static int getAccuracy(DeterminationVectorProvider provider, List<Instrument> indexes) {
        try {
            List<Vector> filteredVectors = filterVectors(provider.getVectors(), indexes);

            int maxPoints = filteredVectors.size() * MAX_POINTS;
            int points = 0;
            for (Vector vector : filteredVectors) {
                points += getPoints(vector);
            }

            return points * 100 / maxPoints;
        } catch (Exception e) {
//            throw new ClassificatorRuntimeException("Illegal provider:\n" + provider, e);
            return 0;
        }
    }

    private static List<Vector> filterVectors(List<Vector> vectors, List<Instrument> indexes) {
        List<Vector> filteredVectors = new ArrayList<Vector>();

        List<String> indexesSymbols = new ArrayList<String>();
        for (Instrument index : indexes)
            indexesSymbols.add(index.getSymbol());

        for (Vector vector : vectors) {
            String instrumentSymbol = vector.getLabel();
            String[] standardIndexes = STANDARD.get(instrumentSymbol);
            if (standardIndexes != null) {
                for (String standardIndex : standardIndexes) {
                    if (indexesSymbols.contains(standardIndex)) {
                        filteredVectors.add(vector);
                        break;
                    }
                }
            }
        }
        return filteredVectors;
    }

    private static boolean checkSignificance(Vector vector) {
        List<Entry<String, Double>> sortedIndexes = vector.getSortedCoordinates();

        int significantValuesCount = 0;
        Iterator<Entry<String, Double>> iterator = sortedIndexes.iterator();

        while (iterator.hasNext()
                && MathUtil.percent(iterator.next().getValue()) >= SIGNIFICANCE_THRESHOLD) {
            significantValuesCount++;
        }

        return significantValuesCount > 0 && significantValuesCount < 4;
    }

    private static DeterminationVectorProvider getMostAccurateVectorProvider(InstrumentLoader.Period period,
                                                                              List<Instrument> instruments,
                                                                              List<Instrument> indexes)
    {
        DeterminationVectorProvider mostAccurateVectorProvider = new DeterminationVectorProvider(instruments, indexes,
                QuoteDate.valueOf("01/01/2011"), 4, 1);

        int processCounter = 0;
        int stickyProcess = 0;
        Set<DeterminationVectorProvider> providersVariety = getProvidersVariety(period, instruments, indexes);
        for (DeterminationVectorProvider provider : providersVariety) {
            int mostAccuracy = getAccuracy(mostAccurateVectorProvider, indexes);
            int providerAccuracy = getAccuracy(provider, indexes);

            if (mostAccuracy < providerAccuracy) {
                mostAccurateVectorProvider = provider;
            }

            int process = processCounter++ * 100 / providersVariety.size();
            if (stickyProcess != process) {
                System.out.println("Done: " + process + "%");
                stickyProcess = process;
            }
        }

        return mostAccurateVectorProvider;
    }

    private static Set<DeterminationVectorProvider> getProvidersVariety(Period period, List<Instrument> instruments,
                                                                         List<Instrument> indexes)
    {
        Set<DeterminationVectorProvider> providers = new HashSet<DeterminationVectorProvider>();

        QuoteDate periodStart = QuoteDate.valueOf("01/01/2011");
        int periodStep = 30;
        int periodLength = 1;
        int historySize = period.getLength();

        for (int div = 2; periodStep > 0; periodStep /= div, div++) {
            for (int i = 0; i < 8; i++) {
                periodLength = Math.min(historySize / periodStep, periodLength * 2);

                providers.add(new DeterminationVectorProvider(instruments, indexes, periodStart, periodLength, periodStep));
            }
        }

        return providers;
    }

    public static void main(String[] args) {
//        getMostAccurateProvider();
        System.out.println("Accuracy = " + getAccuracy(
                new DeterminationVectorProvider(
                    InstrumentLoader.SAMPLE_INSTRUMENTS,
                    InstrumentLoader.DJ_INDEXES,
                    QuoteDate.valueOf("01/01/2011"),
                    30,
                    1),
                InstrumentLoader.DJ_INDEXES));
    }

    private static void getMostAccurateProvider() {
        try {
            List<Instrument> instruments = InstrumentLoader.loadInstruments(IOUtil.FIVE_YEARS_INSTRUMENTS_DATA_FILE_PATH, Period.DEFAULT);
//            List<Instrument> indexes = InstrumentLoader.loadInstruments(IOUtil.INDEXES_DATA_FILE_PATH, Period.DEFAULT);
            List<Instrument> indexes = InstrumentLoader.loadInstruments(IOUtil.SP_INDEXES_FILE_PATH, Period.DEFAULT);
            Period period = new Period(252, QuoteDate.valueOf("01/01/2011"));
            final DeterminationVectorProvider mostAccurateVectorProvider = getMostAccurateVectorProvider(period,
                    instruments, indexes);

            System.out.println("\nMost accurate provider:");
            System.out.println(mostAccurateVectorProvider);
            System.out.println("Accuracy is " + getAccuracy(mostAccurateVectorProvider, indexes));
            printClassification(mostAccurateVectorProvider);
        } catch (Exception e) {
            System.out.println("System error. Caused by ");
            e.printStackTrace(System.out);
        }
    }
}
